#ifndef ASFINGER_PBRPC_CORE_HELPERS_H_
#define ASFINGER_PBRPC_CORE_HELPERS_H_

#include <cstdlib>
#include <cwctype>

#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "core/config.h"

namespace asfinger {
namespace pbrpc {

namespace pb = google::protobuf;

/*!
 * 去除输入字符串的指定后缀
 * @param [out] filename 输入字符串
 * @param suffix 后缀
 * @return true 成功
 * @return false 失败
 */
inline bool StripSuffix(std::string &filename, const std::string &suffix) {
  if (filename.length() >= suffix.length()) {
    size_t suffix_pos = filename.length() - suffix.length();
    if (0 == filename.compare(suffix_pos, std::string::npos, suffix)) {
      filename.resize(filename.size() - suffix.size());
      return true;
    }
  }

  return false;
}

/*!
 * 去除输入字符串的指定前缀
 * @param [out] name 输入字符串
 * @param prefix 前缀
 * @return true 成功
 * @return false 失败
 */
inline bool StripPrefix(std::string &name, const std::string &prefix) {
  if (name.length() >= prefix.length()) {
    if (prefix == name.substr(0, prefix.size())) {
      name = name.substr(prefix.size());
      return true;
    }
  }
  return false;
}

/*!
 * 去除协议文件后缀
 * @param filename 文件名
 * @return 操作后的文件名
 */
inline std::string StripProto(std::string filename) {
  if (!StripSuffix(filename, ".protodevel")) {
    StripSuffix(filename, ".proto");
  }
  return filename;
}

/*!
 * 字符串替换
 * @param str 原字符串
 * @param from 被替换的字符串
 * @param to 要替换的字符串
 * @param replace_all 是否全部替换标志 默认全替换
 * @return 操作完后的字符串
 */
inline std::string StringReplace(std::string str,
                                 const std::string &from,
                                 const std::string &to,
                                 bool replace_all = true) {
  size_t pos = 0; // 被替换的字符串位置

  do {
    pos = str.find(from, pos);
    if (pos == std::string::npos) {
      break;
    }
    str.replace(pos, from.length(), to);
    pos += to.length();
  } while (replace_all);

  return str;
}

/*!
 * 分词器
 * @param input 输入
 * @param delimiters 分隔符
 * @return 分隔后的集合
 */
inline std::vector<std::string> Tokenize(const std::string &input,
                                         const std::string &delimiters) {
  std::vector<std::string> tokens;
  size_t pos, last_pos = 0;

  for (;;) {
    bool done = false;
    pos = input.find_first_of(delimiters, last_pos);
    if (pos == std::string::npos) {
      done = true;
      pos = input.length();
    }

    tokens.push_back(input.substr(last_pos, pos - last_pos));
    if (done)
      return std::move(tokens);

    last_pos = pos + 1;
  }
}

/*!
 * 首字母转大写
 * @param s 输入字符串
 * @return 转换后的字符串
 */
inline std::string CapitalizeFirstLetter(std::string str) {
  if (str.empty()) {
    return str;
  }
  str[0] = std::toupper(str[0]);
  return str;
}

/*!
 * 首字母转小写
 * @param s 输入字符串
 * @return 转换后的字符串
 */
inline std::string LowercaseFirstLetter(std::string str) {
  if (str.empty()) {
    return str;
  }
  str[0] = std::tolower(str[0]);
  return str;
}

/*!
 * 小写下划线转大驼峰
 * @param str 输入字符串
 * @return 大驼峰后的字符串
 */
inline std::string LowerUnderscoreToUpperCamel(std::string str) {
  std::vector<std::string> tokens = Tokenize(str, "_");
  std::string result;
  for (auto &token : tokens) {
    result += CapitalizeFirstLetter(token);
  }
  return result;
}

/*!
 * 文件名转换大驼峰
 * @param file 文件路径
 * @param include_package_path 是否包含包名路径 默认包含
 * @return 大驼峰文件名
 */
inline std::string FileNameInUpperCamel(
    const pb::FileDescriptor *file,
    bool include_package_path = true) {
  std::vector<std::string> tokens = Tokenize(StripProto(file->name()), "/");
  std::string result;
  if (include_package_path) {
    for (unsigned int i = 0; i < tokens.size() - 1; i++) {
      result += tokens[i] + "/";
    }
  }
  result += LowerUnderscoreToUpperCamel(tokens.back());
  return result;
}

/*!
 * 方法类型
 */
enum MethodType {
  kMethodType_No_Streaming, /*!< 非缓冲流  */
  kMethodType_Client_Streaming, /*!< 客户端缓冲流  */
  kMethodType_Server_Streaming, /*!< 服务器缓冲流  */
  kMethodType_Bidi_Streaming, /*!< 双向缓冲流  */
};

/*!
 * 获取文件类型
 * @param method protobuf文件
 * @return 文件类型
 */
inline MethodType GetMethodType(const pb::MethodDescriptor *method) {
  if (method->client_streaming()) {
    if (method->server_streaming()) {
      return kMethodType_Bidi_Streaming;
    } else {
      return kMethodType_Client_Streaming;
    }
  } else {
    if (method->server_streaming()) {
      return kMethodType_Server_Streaming;
    } else {
      return kMethodType_No_Streaming;
    }
  }
}

/*!
 * 分隔字符串
 * @param str 要分隔的字符串
 * @param delim 分隔符
 * @param append_to 结果集
 */
inline void Split(const std::string &str, char delim,
                  std::vector<std::string> &append_to) {
  std::istringstream iss(str);
  std::string piece;
  while (std::getline(iss, piece)) {
    append_to.emplace_back(piece);
  }
}

/*!
 * 注释类型
 */
enum CommentType {
  kCommentType_Leading, /*!< 前导 */
  kCommentType_Trailing, /*!< 尾部 */
  kCommentType_Leading_Detached, /*!< 独立前导 */
};

/*!
 * 获取所有原始注释将没有换行符的每一行追加到结果集中
 * @tparam DescriptorType protobuf描述类型
 * @param desc 描述类型
 * @param type 注释类型
 * @param out 结果集
 */
template<typename DescriptorType>
inline void GetComment(const DescriptorType *desc, CommentType type,
                       std::vector<std::string> &out) {
  pb::SourceLocation location;
  if (!desc->GetSourceLocation(&location)) {
    return;
  }
  if (kCommentType_Leading == type ||
      kCommentType_Trailing == type) {
    const std::string &comments = kCommentType_Leading == type
                                  ? location.leading_comments
                                  : location.trailing_comments;
    Split(comments, '\n', out);
  } else if (kCommentType_Leading_Detached == type) {
    for (auto &leading_detached_comment : location.leading_detached_comments) {
      Split(leading_detached_comment, '\n', out);
      out.emplace_back("");
    }
  } else {
    std::cerr << "Unknown comment type " << type << std::endl;
    std::abort();
  }
}

/*!
 * 获取所有原始注释将没有换行符的每一行追加到结果集中
 * 对于文件前导和独立前导注释，语法上返回注释
 * 尾部注释不返回任何内容
 * @param desc 描述类型
 * @param type 注释类型
 * @param out 结果集
 */
template<>
inline void GetComment(const pb::FileDescriptor *desc,
                       CommentType type, std::vector<std::string> &out) {
  if (kCommentType_Trailing == type) {
    return;
  }
  pb::SourceLocation location;
  std::vector<int> path;
  path.push_back(pb::FileDescriptorProto::kSyntaxFieldNumber);
  if (!desc->GetSourceLocation(path, &location)) {
    return;
  }
  if (kCommentType_Leading == type) {
    Split(location.leading_comments, '\n', out);
  } else if (kCommentType_Leading_Detached == type) {
    for (auto &leading_detached_comment : location.leading_detached_comments) {
      Split(leading_detached_comment, '\n', out);
      out.emplace_back("");
    }
  } else {
    std::cerr << "Unknown comment type " << type << std::endl;
    std::abort();
  }
}

/*!
 * 生成带前缀注释
 * @param in 原生注释
 * @param prefix 前缀
 * @return 带前缀的注释
 */
inline std::string GenerateCommentsWithPrefix(
    const std::vector<std::string> &in, const std::string &prefix) {
  std::ostringstream oss;
  for (const auto &elem : in) {
    if (elem.empty()) {
      oss << prefix << "\n";
    } else if (elem[0] == ' ') {
      oss << prefix << elem << "\n";
    } else {
      oss << prefix << " " << elem << "\n";
    }
  }
  return oss.str();
}

/*!
 * 获取带前缀的注释
 * @tparam DescriptorType protobuf描述类型
 * @param desc 描述类型
 * @param leading 是否为前导注释
 * @param prefix 前缀
 * @return 带前缀的注释
 */
template<typename DescriptorType>
inline std::string GetPrefixedComments(const DescriptorType *desc, bool leading,
                                       const std::string &prefix) {
  std::vector<std::string> out;
  if (leading) {
    GetComment(desc, kCommentType_Leading_Detached,
               out);
    std::vector<std::string> leading;
    GetComment(desc, kCommentType_Leading,
               leading);
    out.insert(out.end(), leading.begin(), leading.end());
  } else {
    GetComment(desc, kCommentType_Trailing, out);
  }
  return GenerateCommentsWithPrefix(out, prefix);
}

/*!
 * 点转作用域冒号
 * @param name 输入字符串
 * @return 处理完的字符串
 */
inline std::string DotsToColons(const std::string &name) {
  return StringReplace(name, ".", "::");
}

/*!
 * 点转下划线
 * @param name 输入字符串
 * @return 处理完的字符串
 */
inline std::string DotsToUnderscores(const std::string &name) {
  return StringReplace(name, ".", "_");
}

/*!
 * 获取类名称
 * @param descriptor protobuf描述符
 * @param qualified 是否合格
 * @return 类名称
 */
inline std::string ClassName(const pb::Descriptor *descriptor,
                             bool qualified) {
  // 找到镶嵌了描述符的顶级消息的描述符
  const pb::Descriptor *outer = descriptor;
  while (nullptr != outer->containing_type())
    outer = outer->containing_type();

  const std::string &outer_name = outer->full_name();
  std::string inner_name = descriptor->full_name().substr(outer_name.size());

  if (qualified) {
    return "::" + DotsToColons(outer_name) + DotsToUnderscores(inner_name);
  } else {
    return outer->name() + DotsToUnderscores(inner_name);
  }
}

/*!
 * 获取c++注释
 * @tparam DescriptorType protobuf描述类型
 * @param desc 描述类型
 * @param leading 是否为前导注释
 * @return c++注释
 */
template<typename DescriptorType>
inline std::string GetCppComments(const DescriptorType *desc, bool leading) {
  return GetPrefixedComments(desc, leading, "//");
}

} //! namespace pbrpc
} //! namespace asfinger

#endif //ASFINGER_PBRPC_CORE_HELPERS_H_